package io.github.yotta.export.web.autoconfiguration;

import io.github.yotta.common.api.model.ApiPageRequest;
import io.github.yotta.common.api.model.Paging;
import io.github.yotta.export.web.anno.ExportExcelData;
import io.github.yotta.export.web.iface.DefaultExportParamConvert;
import io.github.yotta.export.web.iface.ExportParamConvert;
import io.github.yotta.export.web.iface.MethodSpringExcelDataExporter;
import io.github.yotta.export.web.iface.SpringExcelDataExporter;
import io.github.yotta.export.web.manager.ExcelExporterRegister;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.annotation.AnnotatedElementUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Map;

/**
 * @author yotta
 * @date 2023/10/30 14:15
 */
@Slf4j
@Configuration
@SuppressWarnings({"rawtypes", "unchecked", "unused"})
public class DataExportBeanPostProcessorConfiguration implements SmartInitializingSingleton, ApplicationContextAware {

    private final ExcelExporterRegister excelExporterRegister;

    private ApplicationContext applicationContext;


    public DataExportBeanPostProcessorConfiguration(ExcelExporterRegister excelExporterRegister) {
        this.excelExporterRegister = excelExporterRegister;
    }


    @Bean
    public BeanPostProcessor beanPostProcessorExcelDataExporter() {

        return new BeanPostProcessor() {
            @Override
            public Object postProcessAfterInitialization(@NotNull Object bean, @NotNull String beanName) throws BeansException {
                if (bean instanceof SpringExcelDataExporter) {
                    excelExporterRegister.register(beanName, (SpringExcelDataExporter) bean);
                }
                return bean;
            }

        };
    }

    @Bean
    public BeanPostProcessor beanPostProcessorAnnoExcelDataExporter() {

        return new BeanPostProcessor() {

            @Override
            public Object postProcessAfterInitialization(@NotNull Object bean, @NotNull String beanName) throws BeansException {
                Class<?> targetClass = AopUtils.getTargetClass(bean);
                final Map<Method, ExportExcelData> methodExcelDataExporterMap = MethodIntrospector.selectMethods(targetClass,
                        (MethodIntrospector.MetadataLookup<ExportExcelData>) method ->
                                AnnotatedElementUtils.findMergedAnnotation(method, ExportExcelData.class)
                );
                for (Map.Entry<Method, ExportExcelData> entry : methodExcelDataExporterMap.entrySet()) {
                    final Method method = entry.getKey();
                    final ExportExcelData excelDataExporterAnno = entry.getValue();
                    processExcelDataExporter(method, excelDataExporterAnno, bean);
                }
                return bean;
            }

            private void processExcelDataExporter(Method method,
                                                  ExportExcelData excelDataExporterAnno,
                                                  Object bean) {
                checkMethod(method);
                ExportParamConvert exportParamConvert;
                if (excelDataExporterAnno.convert() != DefaultExportParamConvert.class) {
                    exportParamConvert = applicationContext.getBean(excelDataExporterAnno.convert());
                } else {
                    Class<? extends ApiPageRequest> type = (Class<? extends ApiPageRequest>) method.getParameters()[0].getType();
                    exportParamConvert = new DefaultExportParamConvert<>(type);
                }
                MethodSpringExcelDataExporter<?, ?> dataExporter = new MethodSpringExcelDataExporter<>(excelDataExporterAnno, bean, method, exportParamConvert);
                excelExporterRegister.register(excelDataExporterAnno.name(), dataExporter);
            }

            private void checkMethod(Method method) {
                final Class<?> returnType = method.getReturnType();
                if (!returnType.isAssignableFrom(Paging.class)) {
                    throw new IllegalStateException("method returnType must be " + Paging.class.getName());
                }
                final Parameter[] parameters = method.getParameters();
                if (parameters == null || !ApiPageRequest.class.isAssignableFrom(parameters[0].getType())) {
                    throw new IllegalStateException("method first param must be " + ApiPageRequest.class.getName());
                }
            }


            /*
             * AnnotationUtils.getRepeatableAnnotations does not look at interfaces
             */
            private ExportExcelData findExcelDataExporterAnnotations(Method method) {
                return AnnotatedElementUtils.findMergedAnnotation(method, ExportExcelData.class);
            }
        };
    }

    @Override
    public void afterSingletonsInstantiated() {

    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
